package com.itheima.executor;

import com.itheima.config.BoundSql;
import com.itheima.pojo.Configuration;
import com.itheima.pojo.MappedStatement;
import com.itheima.utils.GenericTokenParser;
import com.itheima.utils.ParameterMapping;
import com.itheima.utils.ParameterMappingTokenHandler;

import javax.sql.DataSource;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {
    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception {
        //1.加载驱动，获取数据库连接
        DataSource dataSource = configuration.getDataSource();
        connection = dataSource.getConnection();
        //2.获取PreparedStatement预编译对象
        String sql = mappedStatement.getSql();
        //将sql语句的占位符换成? 同时#{}里面的值要保存下来
        BoundSql boundSql = getBoundSql(sql);
        String finalSql = boundSql.getFinalSql();
        preparedStatement = connection.prepareStatement(finalSql);
        //3.参数进行赋值
        String parameterType = mappedStatement.getParameterType();
        if(parameterType!=null) {
            Class<?> parameterTypeClass = Class.forName(parameterType);
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                // 取出之前在#{}里面的值
                String paramName = parameterMapping.getContent();
                //通过反射得到param中对应的值
                Field declaredField = parameterTypeClass.getDeclaredField(paramName);
                //暴力访问
                declaredField.setAccessible(true);
                //获取param对象中名字为declaredField的值
                Object value = declaredField.get(param);
                //赋值占位符
                preparedStatement.setObject(i + 1, value);
            }
        }
        //4.执行sql发起查询
        resultSet = preparedStatement.executeQuery();
        //5.处理返回结果集
        List<E> list = new ArrayList();
        while (resultSet.next()) {
            //元数据信息
            ResultSetMetaData metaData = resultSet.getMetaData();
            String resultType = mappedStatement.getResultType();
            Class<?> resultTypeClass = Class.forName(resultType);
            //得到要封装的实体对象
            Object o = resultTypeClass.newInstance();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //获取字段名
                String columnName = metaData.getColumnName(i);
                //以及字段的值
                Object value = resultSet.getObject(columnName);
                //属性描述器：通过API方法获取某个属性的读写方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                //得到写的方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            list.add((E) o);
        }
        return list;
    }


    /**
     * 将#{}替换成?  同时将#{}里面的值进行保存
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //1.创建标记处理器：配合标记解析器完成标记处理解析工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        //2.创建标记解析器
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //3.将#{}替换成?  同时将#{}里面的值进行保存在ParameterMapping中
        String finalSql = genericTokenParser.parse(sql);
        //#{}里面的值的集合
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(finalSql, parameterMappings);
        return boundSql;
    }

    /**
     * 进行资源的释放
     */
    @Override
    public void close() {
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(preparedStatement!=null){
            try {
                preparedStatement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}
